package com.gitee.wsl.doc.prosemirror.transform.map

import com.gitee.wsl.ext.base.ifValue
import kotlinx.serialization.json.Json

const val FACTOR_16 = 2 shl 15
const val LOWER_16 = 0xffff

fun makeRecover(index: Int, offset: Int): Int = index + offset * FACTOR_16
fun recoverIndex(value: Int): Int = value and LOWER_16
fun recoverOffset(value: Int): Int = (value - (value and LOWER_16)) / FACTOR_16


// A map describing the deletions and insertions made by a step, which can be used to find the
// correspondence between positions in the pre-step version of a document and the same position in
// the post-step version.
class StepMap(
    // Create a position map. The modifications to the document are represented as an array of
    // numbers, in which each group of three represents a modified chunk as
    // `[start, oldSize, newSize]`.
    internal val ranges: List<Int>,
    internal val inverted: Boolean = false
) : Mappable {
    internal fun recover(value: Int): Int {
        var diff = 0
        val index = recoverIndex(value)
        if (!this.inverted) {
            for (i in 0 until index) {
                diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1]
            }
        }
        return this.ranges[index * 3] + diff + recoverOffset(value)
    }

    override fun mapResult(pos: Int, assoc: Int): MapResult {
        return this._map(pos, assoc, false) as MapResult
    }

    override fun map(pos: Int, assoc: Int): Int {
        return _map(pos, assoc, true) as Int
    }

    @Suppress("ktlint:standard:function-naming")
    internal fun _map(pos: Int, assoc: Int, simple: Boolean): Any {
        var diff = 0
        val oldIndex = if (this.inverted) 2 else 1
        val newIndex = if (this.inverted) 1 else 2
        for (i in 0 until this.ranges.size step 3) {
            val start = this.ranges[i] - (if (this.inverted) diff else 0)
            if (start > pos) break
            val oldSize = this.ranges[i + oldIndex]
            val newSize = this.ranges[i + newIndex]
            val end = start + oldSize
            if (pos <= end) {
                val side = when {
                    oldSize == 0 -> assoc
                    pos == start -> -1
                    pos == end -> 1
                    else -> assoc
                }
                val result = start + diff + (if (side < 0) 0 else newSize)
                if (simple) return result
                val recover = if (pos == (if (assoc < 0) start else end)) {
                    null
                } else {
                    makeRecover(i / 3, pos - start)
                }
                var del = if (pos == start) DEL_AFTER else if (pos == end) DEL_BEFORE else DEL_ACROSS
                if (if (assoc < 0) pos != start else pos != end) del = del or DEL_SIDE
                return MapResult(result, del, recover)
            }
            diff += newSize - oldSize
        }
        return if (simple) pos + diff else MapResult(pos + diff, 0, null)
    }

    internal fun touches(pos: Int, recover: Int): Boolean {
        var diff = 0
        val index = recoverIndex(recover)
        val oldIndex = if (this.inverted) 2 else 1
        val newIndex = if (this.inverted) 1 else 2
        for (i in ranges.indices step 3) {
            val start = this.ranges[i] - (if (this.inverted) diff else 0)
            if (start > pos) break
            val oldSize = this.ranges[i + oldIndex]
            val end = start + oldSize
            if (pos <= end && i == index * 3) return true
            diff += this.ranges[i + newIndex] - oldSize
        }
        return false
    }

    // Calls the given function on each of the changed ranges included in this map.
    fun forEach(f: (oldStart: Int, oldEnd: Int, newStart: Int, newEnd: Int) -> Unit) {
        val oldIndex = if (this.inverted) 2 else 1
        val newIndex = if (this.inverted) 1 else 2

        var diff = 0
        for (i in ranges.indices step 3) {
            val start = this.ranges[i]
            val oldStart = start - (if (this.inverted) diff else 0)
            val newStart = start + (if (this.inverted) 0 else diff)
            val oldSize = this.ranges[i + oldIndex]
            val newSize = this.ranges[i + newIndex]
            f(oldStart, oldStart + oldSize, newStart, newStart + newSize)
            diff += newSize - oldSize
        }
    }

    // Create an inverted version of this map. The result can be used to map positions in the post-step document to
    // the pre-step document.
    fun invert() = StepMap(this.ranges, !this.inverted)

    override fun toString(): String {
        return (if (this.inverted) "-" else "") + Json.Default.encodeToString(this.ranges)
    }

    companion object {
        // A StepMap that contains no changed ranges.
        val empty = StepMap(emptyList())

        // Create a map that moves all positions by offset `n` (which may be negative). This can be
        // useful when applying steps meant for a sub-document to a larger document, or vice-versa.
        fun offset(n: Int) = if (n == 0) empty else StepMap(if (n < 0) listOf(0, -n, 0) else listOf(0, 0, n))
    }
}

data class PositionRange(
    val start:Int = -1,
    val oldSize:Int = 0,
    val newSize:Int = 0
){
    operator fun get(index:Int):Int{
       return  when(index){
            0->start
            1->oldSize
            2->newSize
            else -> throw IllegalArgumentException()
        }
    }

    fun getRange(inverted:Boolean = false) = inverted.ifValue( newSize to oldSize,oldSize to newSize)
}

infix fun Pair<Int,Int>.at(start: Int) = PositionRange(start,this.first,this.second)